NOIP2013 普及组

T1:计数问题

题目描述

试计算在区间 1 n 的所有整数中,数字 x(0 ≤ x ≤ 9) 共出现了多少次?例如,在 1 11 中,即在 1,2,3,4,5,6,7,8,9,10,11 中,数字 1 出现了 4 次。

输入格式

2 个整数 n,x ,之间用一个空格隔开。

输出格式

1 个整数,表示 x 出现的次数。

输入输出样例

输入样例 #1

11 1

输出样例 #1

4

说明/提示

对于 100\% 的数据, 1≤ n ≤ 1,000,000,0 ≤ x ≤ 9

NOIP2013普及组第一题

题解:

​​​​ ​本题直接模拟就可以。在读入 n,\ x 之后,从 1\ \sim\ n 枚举数字,然后将枚举的数字的每一位与 x 进行比较,如果这一位等于 x 的话那么答案便 +1

#include <iostream>
using namespace std;
int main() {
    int n, x;
    cin >> n >> x;
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        int p = i;
        while (p > 0) {
            if (p % 10 == x)
                ans++;
            p /= 10;
        }
    }
    cout << ans << endl;
    return 0;
}

T2:表达式求值

题目描述

给定一个只包含加法和乘法的算术表达式,请你编程计算表达式的值。

输入格式

一行,为需要你计算的表达式,表达式中只包含数字、加法运算符“ + ”和乘法运算符“ \times ”,且没有括号,所有参与运算的数字均为 0 2^{31}-1 之间的整数。

输入数据保证这一行只有 0-9 + \times 12 种字符。

输出格式

一个整数,表示这个表达式的值。

注意:当答案长度多于 4 位时,请只输出最后 4 位,前导 0 不输出。

输入输出样例

输入样例 #1

1+1*3+4

输出样例 #1

8

输入样例 #2

1+1234567890*1

输出样例 #2

7891

输入样例 #3

1+1000000003*1

输出样例 #3

4

说明/提示

对于 30\% 的数据, 0≤ 表达式中加法运算符和乘法运算符的总数 ≤100

对于 80\% 的数据, 0≤ 表达式中加法运算符和乘法运算符的总数 ≤1000

对于 100\% 的数据, 0≤ 表达式中加法运算符和乘法运算符的总数 ≤100000

NOIP2013普及组第二题

题解:

​​​​ ​按照运算顺序,我们要先计算乘法,再计算加法。

​​​​ ​我的思路是首先算出所有乘法式子的值,然后再开一个 sum 数组来存储所有的值。最后,只需要将所有 sum 数组中的数字加起来就好了。

#include <cstring>
#include <iostream>
using namespace std;
long long sum[100001], k = 1;
int main() {
    string exp;
    getline(cin, exp);
    long long num = 0, all = 1;
    for (long long i = 0; i < exp.length(); i++) {
        if (exp[i] >= '0' && exp[i] <= '9') {
            num = num * 10 + exp[i] - '0';
        } else if (exp[i] == '*'){
            all = (all * num) % 10000;
            num = 0;
        } else {
            all = (all * num) % 10000;
            sum[k] = all;
            k++;
            all = 1;
            num = 0;
        }
    }
    all = (all * num) % 10000;
    sum[k] = all;
    int ans = 0;
    for (long long i = 1; i <= k; i++)
        ans = (ans + sum[i]) % 10000;
    cout << ans << endl;
    return 0;
}

T3:小朋友的数字

题目描述

n 个小朋友排成一列。每个小朋友手上都有一个数字,这个数字可正可负。规定每个小朋友的特征值等于排在他前面(包括他本人)的小朋友中连续若干个(最少有一个)小朋友手上的数字之和的最大值。

作为这些小朋友的老师,你需要给每个小朋友一个分数,分数是这样规定的:第一个小朋友的分数是他的特征值,其它小朋友的分数为排在他前面的所有小朋友中(不包括他本人),小朋友分数加上其特征值的最大值。

请计算所有小朋友分数的最大值,输出时保持最大值的符号,将其绝对值对 p 取模后输出。

输入格式

第一行包含两个正整数 n,p ,之间用一个空格隔开。

第二行包含 n 个数,每两个整数之间用一个空格隔开,表示每个小朋友手上的数字。

输出格式

一个整数,表示最大分数对 p 取模的结果。

输入输出样例

输入样例 #1

5 997 
1 2 3 4 5 

输出样例 #1

21

输入样例 #2

5 7 
-1 -1 -1 -1 -1 

输出样例 #2

-1

说明/提示

Case 1:

小朋友的特征值分别为 1,3,6,10,15 ,分数分别为 1,2,5,11,21 ,最大值 21 997 的模是 21

Case 2:

小朋友的特征值分别为 -1,-1,-1,-1,-1 ,分数分别为 -1,-2,-2,-2,-2 ,最大值 -1 7 的模为 -1 ,输出 -1

对于 50\% 的数据, 1 ≤ n ≤ 1,000,1 ≤ p ≤ 1,000 所有数字的绝对值不超过 1000

对于 100\% 的数据, 1 ≤ n ≤ 1,000,000,1 ≤ p ≤ 10^9 ,其他数字的绝对值均不超过 10^9

NOIP2013普及组第三题

题解:

​​​​ ​对于每一个小朋友的特征值,就是求当前小朋友之前的最长公共子序列。无疑,读一个小朋友求一次最长公共子序列太浪费时间。因此,我们便可以设置一个 maxx 变量来记录当前小朋友之前的最长公共子序列。

​​​​ ​为了保证 maxx 不断更新,我们设置一个 s 数组,其中 s[i] 表示以 i 结尾的最长公共子序列。我们将新读进的数字计作 num ,考虑 s[i] 既可以作为一个子序列的起点,也可以接着之前的子序列继续延伸下去。因此 s[i]\ =\ max(num,\ num\ +\ s[i\ -\ 1]) ,所以 maxx 变量也就更新为 max\ (maxx,\ s[i]) ,这个小朋友的特征值 features[i] 也就是 maxx\ \%\ p 了。

​​​​ ​我们将 maxx 赋值为负无穷,来存储每个小朋友前面的所有人中,得分 + 特征值最高的那个数字。因此, maxx\ =\ max(maxx,\ features[i - 1]\ +\ score[i - 1]) ,同理,当前小朋友的得分也就等于 maxx 了。然后比较一下当前小朋友的得分是不是高于答案 ans ,若 score[i]\ >\ ans 的话, ans 更新为 score[i] 。最后,输出 ans 就OK辣!

#include <iostream>
using namespace std;
long long features[1000001], score[1000001], s[1000001];
int main() {
    long long n, p, maxx = -2147483648, num = 0;
    cin >> n >> p;
    for (long long i = 1; i <= n; i++) {
        cin >> num;
        s[i] = max (num, num + s[i - 1]); //以 i 结尾的最长公共子序列
        maxx = max (maxx, s[i]); // i 之前的最长公共子序列
        features[i] = maxx % p;
    }
    long long ans = score[1] = features[1];
    maxx = -2147483648;
    for (long long i = 2; i <= n; i++) {
        score[i] = maxx = max(maxx, features[i - 1] + score[i - 1]);
        ans = max(maxx, ans) % p;
    }
    cout << ans << endl;
    return 0;
}

T4:车站分级

题目描述

一条单向的铁路线上,依次有编号为 1, 2, …, n n 个火车站。每个火车站都有一个级别,最低为 1 级。现有若干趟车次在这条线路上行驶,每一趟都满足如下要求:如果这趟车次停靠了火车站 x ,则始发站、终点站之间所有级别大于等于火车站 x 的都必须停靠。(注意:起始站和终点站自然也算作事先已知需要停靠的站点)

例如,下表是 5 趟车次的运行情况。其中,前 4 趟车次均满足要求,而第 5 趟车次由于停靠了 3 号火车站( 2 级)却未停靠途经的 6 号火车站(亦为 2 级)而不满足要求。

现有 m 趟车次的运行情况(全部满足要求),试推算这 n 个火车站至少分为几个不同的级别。

输入格式

第一行包含 2 个正整数 n, m ,用一个空格隔开。

i + 1 (1 ≤ i ≤ m) 中,首先是一个正整数 s_i(2 ≤ s_i ≤ n) ,表示第 i 趟车次有 s_i 个停靠站;接下来有 s_i 个正整数,表示所有停靠站的编号,从小到大排列。每两个数之间用一个空格隔开。输入保证所有的车次都满足要求。

输出格式

一个正整数,即 n 个火车站最少划分的级别数。

输入输出样例

输入样例 #1

9 2 
4 1 3 5 6 
3 3 5 6 

输出样例 #1

2

输入样例 #2

9 3 
4 1 3 5 6 
3 3 5 6 
3 1 5 9 

输出样例 #2

3

说明/提示

对于 20\% 的数据, 1 ≤ n, m ≤ 10

对于 50\% 的数据, 1 ≤ n, m ≤ 100

对于 100\% 的数据, 1 ≤ n, m ≤ 1000

NOIP2013普及组第四题

题解:

​​​​ ​这道题我们可以理解为凡是停靠过的站,那么这个站就比没停靠过的站级别高。

​​​​ ​因此,可以确定部分车站之间的两两大小关系,我们从级别低的车站 i 向级别高的车站 j 连边, topo[i][j] == 1 则代表 j 的级别比 i 的级别高。

​​​​ ​然后,怎么确定最少划分的级别数呢?这就需要 拓扑排序 了。

那么,啥是拓扑排序呢?

The canonical application of topological sorting is in scheduling a sequence of jobs or tasks based on their dependencies. The jobs are represented by vertices, and there is an edge from x to y if job x must be completed before job y can be started (for example, when washing clothes, the washing machine must finish before we put the clothes in the dryer). Then, a topological sort gives an order in which to perform the jobs.

(摘自维基百科

​​​​ ​👆上面这段话说白了,就是如果干 y 工作之前必须先完成 x 工作(例如,你在烘干衣服之前必须要先把衣服洗干净,不然烘干也没用),而完成的这个顺序就是拓扑排序。

​​​​ ​因此,我们便可以利用拓扑排序的思想,不断删除停靠火车数为 0 的车站,然后把比这个车站高一级的所有车站 topo[i][j] 赋值为 0 ,将 j 车站中停靠的火车数 -1 ,最后输出一共执行了多少次操作就可以了。

#include <cstring>
#include <iostream>
using namespace std;
int st[1001], t[1001];
int topo[1001][1001], level[1001]; //两站之间的拓扑序(若 topo[i][j] == 1 则代表 j 的级别比 i 的级别高);每站的级别
bool station[1001], cut[1001]; //判断本站有无火车停靠;是否已经删除这个点
int main() {
    int n, m;
    cin >> n >> m;
    
    for (int i = 1; i <= m; i++) {
        memset(station, false, sizeof(station)); //一开始没有任何火车停靠
        int s;
        cin >> s;
        for (int j = 1; j <= s; j++) {
            cin >> st[j];
            station[st[j]] = true; // 表示本站有火车停靠
        }
        for (int j = st[1]; j <= st[s]; j++) { //枚举每个站点
            if (station[j] == false) { //没有火车停靠本站点
                for (int k = 1; k <= s; k++) {
                    if (topo[j][st[k]] == 0) {
                        topo[j][st[k]] = 1; //k 的级别比 j 的级别高
                        level[st[k]]++;
                    }
                }
            }
        }
    }
    
    memset(cut, false, sizeof(cut)); //一开始没有任何火车停靠
    int ans = 0, top = 0;
    
    do { //不断删点、边
        top = 0;
        for (int i = 1; i <= n; i++) {
            if (level[i] == 0 && cut[i] == false) { //这个车站的级别为 0 且没有删去这个点
                cut[i] = true;
                top++;
                t[top] = i;
            }
        }
        for (int i = 1; i <= top; i++) {
            for (int j = 1; j <= n; j++) {
                if (topo[t[i]][j] == 1) {
                    topo[t[i]][j] = 0;
                    level[j]--;
                }
            }
        }
        ans++;
    } while(top != 0);
    
    cout << ans - 1 << endl; // ans 会多加一次
    return 0;
}